import sys

if sys.version_info[0] == 2:
    from StringIO import StringIO
else:
    from io import StringIO

from ..libmoops import (TEXT_ADDRESS, DATA_ADDRESS, sign_extend)
from .registers import registers, PipelineReg
from .control import control
from .alu import alu
from .readBinary import ReadBinary
from .memory import Memory
from .fpu import Fpu
from .mdu import mdu
from .coprocessor0 import Coprocessor0


class cpu(object):
    
    ##pipeline options
    exceptionFile = "moops/exception.s"
    pipeline = True
    forwarding_unit = True
    hazardDetection = True
    
    def __init__(self):
        #self.instruction_register = registers(0)
        self.control = control()
        self.register = registers(32)
        self.ifid = PipelineReg()
        self.idex = PipelineReg()
        self.exmem = PipelineReg()
        self.memwb = PipelineReg()
        self.hilo = registers(2)        
        self.alu = alu()
        self.jumpAlu = alu()
        self.memory = Memory()
        self.mdu = mdu()
        self.fpu = Fpu()
        self.cp0 = Coprocessor0()
        
        self.idexMfHiLo = 0
        
        
    def getRegisterState(self):
        return self.register.getRegisterState()

    def getInstructionCount(self):
        return self.instructionCount
        
    def getDataCount(self):
        return self.dataCount

    def getInstruction(self, i):
        return self.memory.readMemory(i)
        
    def getWord(self, i):
        return self.memory.readWord(i)
        
    def getByte(self, i):
        return self.memory.readByte(i)

    def readMemory(self, i):
        return self.memory.readMemory(i)

    def getPc(self):
        return self.pc

    def loadBinary(self, in_data , is_file=True):

        if not is_file: 
            file_ = StringIO(in_data)
        else: 
            file_ = open(in_data, 'rb')

        loadEXE = ReadBinary()
        loadEXE.loadBinary(file_)

        file_.close()

        text = loadEXE.returnText()
        self.instructionCount = len(text)

        self.memory.writeMemory(TEXT_ADDRESS, text)
        
        #temporary until exception.s is made
        self.memory.writeMemory(0x80000000, 0)
        self.memory.writeMemory(0x80000004, 0)
        self.memory.writeMemory(0x80000008, 0b01000010000000000000000000011000)
        self.memory.writeMemory(0x8000000C, 0)
        self.memory.writeMemory(0x80000010, 0)
        self.memory.writeMemory(0x80000014, 0)

        ktext = loadEXE.returnKtext()

        #TODO: make ktext work

        data = loadEXE.returnData()
        self.dataCount = len(data)
        self.memory.writeMemory(DATA_ADDRESS, data)
        print("---datacount---" + hex(self.dataCount))
        
    def __exceptionHandler(self):
        if(self.memwb.signalERET == 1): #give control back to user
            self.pc = self.cp0.register.readRegister(14)
            self.cp0.register.writeRegister(14, 0)
            self.cp0.register.writeRegister(13, 0)
            self.cp0.register.writeRegister(12, self.cp0.register.readRegister(12) >> 5)
        elif ( (self.cp0.register.readRegister(12) & 0xfffff) > 0):
            ##if (self.memwb.signalException == 1):
            if (self.memwb.signalSyscall == 1 ):
                self.cp0.register.writeRegister(14, self.pc) #EPC
                self.pc = 0x80000000
                self.cp0.register.writeRegister(13, 8) # 8 is cause for syscall
                self.cp0.register.writeRegister(12, self.cp0.register.readRegister(12) << 5) #status
            elif (self.memwb.signalDivideByZero == 1):
                self.cp0.register.writeRegister(14, self.pc) #EPC
                self.pc = 0x80000000
                self.cp0.register.writeRegister(13, 13) ##13 is cause for trap (might change)
                self.cp0.register.writeRegister(12, self.cp0.register.readRegister(12) << 5) #status
            elif (self.memwb.signalArithmeticOverflow == 1):
                self.cp0.register.writeRegister(14, self.pc) #EPC
                self.pc = 0x80000000
                self.cp0.register.writeRegister(13, 12) ##12 overflow
                self.cp0.register.writeRegister(12, self.cp0.register.readRegister(12) << 5) #status
            
    

    def initializePipeline(self):
        
        self.cp0.register.writeRegister(12, 0b11111)
        
        ##Deletes memmory and instruction count
        self.instructionCount = 0
        self.dataCount = 0
        
        ##Initializes PC
        self.pc = TEXT_ADDRESS
        #self.nextPC = self.pc + 4
        self.PCSrc = 0 #might not be needed

        ##Initializes Registers
        self.memory.initialize()
        self.register.initialize()
        self.ifid.initialize()
        self.idex.initialize()
        self.exmem.initialize()
        self.memwb.initialize()
        
        #jumps and branches,
        #will not work with atomic, will need to add to pipeline registers for that
        self.PCBranchJump = 0
        self.jumpAddress = 0
        self.branchZero = 0
        
        ##not sure yet
        self.idexOppcode = 0

        ##forwarding
        self.forwardedExmemAluResult = 0
        self.forwardedMemwbAluResult = 0
        
        ##hazard
        self.stallPipeline = 0
        
        ##Probably will be able to delete these...too lazy to look up now
        self.aluB=0
        self.aluOp=0
        
        self.forwardA = 0
        self.forwardB = 0

    ##hazard Detect Unit
    def __hazardDetect(self):
        
        if(self.hazardDetection == True):
            if (self.stallPipeline == 1):
                self.stallPipeline = 0

            ifidRegT = (self.ifid.instruction & 0b00000000000111110000000000000000) >> 16  #instruction[20:16]
            ifidRegS = (self.ifid.instruction & 0b00000011111000000000000000000000) >> 21 #instruction[25:21]
            if(self.idex.signalMemRead == 1 and
               ((self.idex.regT == ifidRegS) or
                (self.idex.regT == ifidRegT))):
                self.stallPipeline = 1
                print("HAZARD FOUND")
                

    ##Forwarding Unit
    def __forwardDetect(self):
        
        if(self.forwarding_unit):
            self.forwardedExmemAluResult = self.exmem.aluResult
            self.forwardedMemwbAluResult = self.memwb.aluResult
            self.forwardedExmemSa = self.exmem.sa
            self.forwardedMemwbSa = self.memwb.sa
            self.forwardA = 0
            self.forwardB = 0
            rtype = False
            itype = False
            exmemItype = False
            exmemRtype = False
            memwbItype = False
            memwbRtype = False
            
            if(self.idex.oppcode == 0):
                rtype = True
            elif (self.idex.oppcode != 0 and (self.idex.oppcode & 0b111110) != 0b000010 and (self.idex.oppcode & 0b111100) != 0b010000):
                itype = True

            if(self.exmem.oppcode == 0):
                exmemRtype = True
            elif ( self.exmem.oppcode != 0 and (self.exmem.oppcode & 0b111110) != 0b000010 and (self.exmem.oppcode & 0b111100) != 0b010000):
                exmemItype = True
                
            if(self.memwb.oppcode == 0):
                memwbRtype = True
            elif ( self.memwb.oppcode != 0 and (self.memwb.oppcode & 0b111110) != 0b000010 and (self.memwb.oppcode & 0b111100) != 0b010000):
                memwbItype = True
                

            ##memwb needs to go first, because exmem is most recent
            ## Register write is true in memwb stage and register destination is not zero
            if (self.memwb.signalRegWrite == 1):
                if(memwbItype): #i type
                    if (self.memwb.regT == self.idex.regS and self.memwb.regT != 0):
                        self.forwardA = 1
                else: #r type
                    if (self.memwb.regD == self.idex.regS and self.memwb.regD != 0):
                        self.forwardA = 1

                if (self.memwb.regD == self.idex.regT and self.memwb.regD != 0):
                    self.forwardB = 1
               
            ##memwb needs to go first, because exmem is most recent
            ## Register write is true in exmem stage and register destination is not zero
            if (self.exmem.signalRegWrite == 1):
                if (exmemItype): #i type
                    if (self.exmem.regT == self.idex.regS and self.exmem.regT != 0):
                        self.forwardA = 2
                else: #r type
                    if (self.exmem.regD == self.idex.regS and self.exmem.regD != 0):
                        self.forwardA = 2
                    
                if (self.exmem.regD == self.idex.regT and self.exmem.regD != 0):
                    self.forwardB = 2
                    

        print("___FORWARD A___ " + hex(self.forwardA))
        print("___FORWARD B___ " + hex(self.forwardB))
        #print("___idexoppcode___" + hex(self.idex.oppcode) + " " + str(itype) + " " + str(rtype))
        print("___exmem Signal Reg Write___" + hex(self.exmem.signalRegWrite))
        print("___exmem regD__" + hex(self.exmem.regD))
        print("___memwb Signal Reg Write___" + hex(self.memwb.signalRegWrite))
        print("___memwb regD__" + hex(self.memwb.regD))

    ##PC Stage
    def __cpuStagePC(self):      
        
        ##if stallPipline == 0, pc gets the next value
        ##if stallPipline == 0, pc keeps old value
        if (self.stallPipeline == 1):
            self.idex.flush()
            self.ifid.initialize()
        else:
            self.pc = self.nextPC
            
    ##Instruction Fetch Stage
    def __cpuStageIF(self):       
        
        print("Current PC " + hex(self.pc))
        
        ##Create ifid pipeline register
        self.ifid = PipelineReg()
        
        ##Instruction Fetch
        self.ifid.PCPlus4 = self.pc + 4
        self.instruction = self.memory.readMemory( self.pc)
        self.fpu.readInstruction(self.instruction)
        self.ifid.instruction = self.instruction
        
        ##next pc logic
        print("<>jump " + hex(self.signalJump) + " address " + hex(self.jumpAddress) + " control branch " + str(self.control.branch) +" zero branch " + str(self.branchZero))
        
        #PCSrc becomes one if a branch must take place
        self.PCSrc = (self.control.branch & self.branchZero) | (self.control.branchne and not self.branchZero)
        if (self.PCSrc==1):
            self.nextPC = self.PCBranchJump
        elif (self.signalJump==1):
            self.nextPC = self.jumpAddress
        else:
            self.nextPC = self.ifid.PCPlus4 

    ##Instruction Decode Stage
    def __cpuStageID(self):
        
        ##pipeline shift
        self.idex=self.ifid

        ##Instruction Decode
        self.oppcode = (self.ifid.instruction & 0b11111100000000000000000000000000) >> 26
        self.function = (self.ifid.instruction & 0b111111) #instruction[5:0]
        self.readReg1 = (self.ifid.instruction & 0b00000011111000000000000000000000) >> 21 #instruction[25:21]
        self.readReg2 = (self.ifid.instruction & 0b00000000000111110000000000000000) >> 16  #instruction[20:16]
        
        ##Sets IDEX Registers from the 32-bit instruction
        self.idex.regD = (self.ifid.instruction & 0b00000000000000001111100000000000) >> 11 #instruction[15:11]
        self.idex.regT = (self.ifid.instruction & 0b00000000000111110000000000000000) >> 16  #instruction[20:16]
        self.idex.regS = (self.ifid.instruction & 0b00000011111000000000000000000000) >> 21 #instruction[25:21]
        
        ##Sets IDEX registers based on register data
        self.idex.readData1 = self.register.readRegister(self.readReg1)
        self.idex.readData2 = self.register.readRegister(self.readReg2)

        ##Sets control bits based on oppcode and functions
        self.control.signalIn(self.oppcode, self.function)
        
        ##Stores the necessary control bits in IDEX Registers
        self.idex.oppcode = self.oppcode
        self.idex.signalRegDst = self.control.getRegDst()
        self.idex.signalAluSrc = self.control.getAluSrc()
        self.idex.signalRegWrite = self.control.getRegWrite()
        self.idex.signalAluOp = self.control.getAluOp()
        self.idex.signalMduOp = self.control.getMduOp()
        self.idex.signalBranchne = self.control.branchne
        self.idex.signalBranch = self.control.getBranch()
        self.idex.signalException = self.control.exception
        self.idex.signalSyscall = self.control.syscall
        self.idex.signalERET = self.control.ERET
        self.idex.signalUpperImmediate = self.control.getUpperImmediate()
        self.idex.signalShift = self.control.getShift()
        self.idex.signalMemWrite = self.control.getMemWrite()
        self.idex.signalMemRead = self.control.getMemRead()
        self.idex.signalMemToReg = self.control.getMemToReg()

        
        self.idexMfHiLo = self.control.getMfHiLo()
        ##Sets idex.immediateValue based on LUI
        if (self.idex.signalUpperImmediate == 0 ):
            self.idex.immediateValue = sign_extend(self.ifid.instruction & 0xFFFF) #instruction[15:0]
        else:
            self.idex.immediateValue = (self.ifid.instruction & 0xFFFF) << 16  
        
        ##Calculates the jump address and stores it in an IDEX Register
        self.signalJump = self.control.getJump()
        self.jumpAddress = ( self.pc & 0b11110000000000000000000000000000) | ( (self.ifid.instruction << 2) & 0b00001111111111111111111111111111)
        
        ##Calculates Branch jump address and sets it in idex registers
        self.jumpAlu.compute(0b0011, self.idex.immediateValue<<2, self.idex.PCPlus4)
        self.PCBranchJump = self.jumpAlu.getResults()
        if (self.idex.readData1 - self.idex.readData2 == 0):
            self.branchZero = 1
        else:
            self.branchZero = 0
        
        if(self.memwb.isMduWrite == 1):
            self.hilo.writeRegister(0, self.memwb.mduHi)
            self.hilo.writeRegister(1, self.memwb.mduLo) 

    ##Instruction Execution Stage
    def __cpuStageEX(self):

        ##pipeline shift
        self.exmem = self.idex
        print("_________LOL___________" + hex(self.exmem.regD))
        ##Sets Sa, used for shift
        sa = (self.idex.immediateValue & 0b00000000000000000000011111000000) >> 6
        self.exmem.sa = sa

        ##Sets Write Register in EXMEM based on instruction type
        if(self.idex.signalRegDst==1):
            self.exmem.writeRegister = self.idex.regD #instruction[15:11]
        else:
            self.exmem.writeRegister = self.idex.regT  #instruction[20:16]

        if (self.forwardA==0):
            if (self.idex.signalShift == 1):
                self.aluA = sa
            elif (self.idexMfHiLo==1):
                self.aluA = self.hilo.readRegister(0)
            elif (self.idexMfHiLo==2):
                self.aluA = self.hilo.readRegister(1)
            else:  #mfhi and mflo are done as "ori $a, mfhi, $0" where $a is whatever the destination is
                self.aluA = self.idex.readData1
        elif (self.forwardA==2):
            if (self.idex.signalShift == 1):
                self.aluA = forwardedExmemSa
            else:
                self.aluA=self.forwardedExmemAluResult
        elif (self.forwardA==1):
            if (self.idex.signalShift == 1):
                self.aluA = forwardedMemwbSa
            else:
                self.aluA=self.forwardedMemwbAluResult
        if (self.forwardB==0):
            if (self.idex.signalAluSrc == 1):
                self.aluB = self.idex.immediateValue
            elif(self.idexMfHiLo==0):
                self.aluB = self.idex.readData2
            else: 
                self.aluB = 0
        elif (self.forwardB==2):
            self.aluB=self.forwardedExmemAluResult
        elif (self.forwardB==1):
            self.aluB=self.forwardedMemwbAluResult
            
        ##Computes results based on opp, and 2 operands
        self.alu.compute(self.idex.signalAluOp, self.aluA, self.aluB)
        self.mdu.compute(self.idex.signalMduOp, self.aluA, self.aluB)  
        
        ##debug, delete later
        print("?")
        print("$rs " + str(self.idex.regS) + "$rt " + str(self.idex.regT))
        print("ALU OPP " + hex(self.idex.signalAluOp))
        print("aluA " + hex(self.aluA))
        print("aluB " + hex(self.aluB))
        print("AluResults " + hex(self.alu.getResults()))
        print("?")
        
        ##Setting EXMEM registers
        self.exmem.aluResult = self.alu.getResults()    
        self.exmem.mduHi = self.mdu.getHi()          # meeeh this is bulky. is there a better way? /ponder
        self.exmem.mduLo = self.mdu.getLo() 
        self.exmem.signalArithmeticOverflow = self.alu.overflow
        self.exmem.signalDivideByZero = self.mdu.divideByZero
        self.exmem.zeroResult = self.alu.getZero()
        print("DIVIDE BY ZERO " + str(self.exmem.signalDivideByZero))
        print("OVERFLOW " + str(self.exmem.signalDivideByZero))


        
        ##MDU - Or all bits of mduOp, meaning there was a result out of the mdu, meaning we need to write to hilo
        if(self.idex.signalMduOp>0):
            self.exmem.isMduWrite = 1
        else:
            self.exmem.isMduWrite = 0

    ##Memory stage
    def __cpuStageMEM(self):
      
        ##pipeline shift
        self.memwb = self.exmem

        ##Read from Memory
        if (self.exmem.signalMemRead==1):
            self.memwb.memReadData = self.memory.readMemory(DATA_ADDRESS + self.exmem.aluResult)

        ##Write to Memory
        if (self.exmem.signalMemWrite==1):
            print("MEM WRITE")
            self.memory.writeMemory(DATA_ADDRESS + self.exmem.aluResult, self.exmem.readData2)
            self.dataCount = self.memory.dataLength
            
        ##Sets Registers based on MEMWB Registers status
        if (self.memwb.signalMemToReg == 1):
            regData = self.memwb.memReadData
        else:
            regData = self.memwb.aluResult
        if (self.memwb.signalRegWrite==1):
            self.register.writeRegister(self.memwb.writeRegister, regData)
            

    def fiveStageStep(self):
        print("\n\nNEW STEP\n")
        self.__forwardDetect()
        self.__cpuStageMEM()
        self.__cpuStageEX()
        self.__cpuStageID()
        self.__cpuStageIF()
        self.__hazardDetect()
        self.__cpuStagePC()
        self.__exceptionHandler()
        print(hex(self.pc))

    def atomicStep(self):
        self.__cpuStagePC()
        self.__cpuStageIF()
        self.__cpuStageID()
        self.__cpuStageEX()
        self.__cpuStageMEM()
        
    def step(self):
        if(self.pipeline):
            self.fiveStageStep()
        else:
            self.atomicStep()
    
    ##This will not work until coprocessor 0 syscalls are complete
    def fiveStage(self):
        while(1):
            self.fiveStageStep()
            break
